package org.kiwi.job.core;

import org.apache.commons.lang3.StringUtils;
import org.kiwi.job.adapter.quartz.QuartzJob;
import org.kiwi.job.constant.ScheduleConstant;
import org.kiwi.job.service.impl.ScheduleJobServiceImpl;
import org.kiwi.job.util.GroovyShellUtil;
import org.kiwi.job.util.MethodInvoker;
import org.kiwi.job.util.MethodInvokerUtils;
import org.kiwi.util.IPUtil;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.util.Assert;

import static org.kiwi.job.constant.ScheduleConstant.*;
import static org.quartz.CronScheduleBuilder.cronSchedule;

/**
 * @email jack.liu.19910921@gmail.com
 * Created by jack on 17/2/19.
 */
public class ScheduleJobManager implements InitializingBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleJobManager.class);

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    @Override
    public void afterPropertiesSet() throws Exception {
        doScheduleJob(buildSystemJob());
    }

    private ScheduleJob buildSystemJob() {
        ScheduleJob scheduleJob = new ScheduleJob();
        scheduleJob.setJobCode(REFRESH_JOB_CODE);
        scheduleJob.setJobName(REFRESH_JOB_NAME);
        scheduleJob.setJobGroup(SYSTEM_GROUP);
        scheduleJob.setJobDesc(REFRESH_JOB_DESC);
        scheduleJob.setJobStatus(JOB_STATUS_ENABLE);
        scheduleJob.setJobCron(CRON_EACH_MINUTE);
        scheduleJob.setIsConcurrent(JOB_CONCURRENT_TRUE);
        scheduleJob.setJobClass(ScheduleJobServiceImpl.class.getName());
        scheduleJob.setJobMethod(REFRESH_JOB_METHOD);
        return scheduleJob;
    }

    public void doScheduleJob(ScheduleJob scheduleJob) throws SchedulerException {
        byte jobStatus = scheduleJob.getJobStatus();
        if (StringUtils.isNotBlank(scheduleJob.getJobScript())) {
            //解析脚本确定jobStatus
            try {
                int value = (int) GroovyShellUtil.evaluate(scheduleJob.getJobScript());
                jobStatus = (byte) value;
            } catch (Exception e) {
                LOGGER.error("解析Groovy脚本出现异常", e);
            }
        }
        switch (jobStatus) {
            case JOB_STATUS_DISABLE:
                deleteJob(scheduleJob.getJobName(), scheduleJob.getJobGroup());
                break;
            case JOB_STATUS_ENABLE:
                validateJob(scheduleJob);
                createJob(scheduleJob);
                break;
            default:
                throw new SchedulerException("不支持的任务状态：" + jobStatus);
        }
    }

    private void deleteJob(String jobName, String jobGroup) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        if (scheduler.checkExists(jobKey)) {
            TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
            if (scheduler.checkExists(triggerKey)) {
                scheduler.resumeTrigger(triggerKey);
                scheduler.unscheduleJob(triggerKey);
                LOGGER.info("停止并移除Trigger[{}]成功", jobName);
            }
            scheduler.deleteJob(jobKey);
            LOGGER.info("停止并移除JOB[{}]成功", jobName);
        }
    }

    public void validateJob(ScheduleJob job) throws SchedulerException {
        Assert.notNull(job.getJobStatus(), "任务状态不能为空");
        Assert.notNull(job.getIsConcurrent(), "是否支持并发不能为空");

        if (StringUtils.isAnyEmpty(job.getJobName(), job.getJobGroup(), job.getJobDesc())) {
            throw new SchedulerException("任务名称或组名或描述不能为空");
        }
        if (StringUtils.isAnyEmpty(job.getJobClass(), job.getJobMethod())) {
            throw new SchedulerException("任务执行类和方法不能为空");
        }
        if (StringUtils.isEmpty(job.getJobCron())) {
            throw new SchedulerException("cron表达式不能为空");
        }
        if (!CronExpression.isValidExpression(job.getJobCron())) {
            throw new SchedulerException("cron表达式不正确");
        }

        try {
            Class clazz = Class.forName(job.getJobClass());
            Object object = applicationContext.getBean(clazz);

            MethodInvoker methodInvoker;
            if (StringUtils.isBlank(job.getHttpPath())) {
                methodInvoker = MethodInvokerUtils.getMethodInvokerByName(object, job.getJobMethod(), false);
            } else {
                methodInvoker = MethodInvokerUtils.getMethodInvokerByName(object, job.getJobMethod(), true, String.class);
            }
            job.setMethodInvoker(methodInvoker);
        } catch (Exception e) {
            LOGGER.error("JOB任务[{}]执行方法[{}.{}]反射异常", job.getJobCode(), job.getJobClass(), job.getJobMethod());
            throw new SchedulerException("类名或方法名不正确");
        }
    }

    private void createJob(ScheduleJob job) throws SchedulerException {

        //判断本机IP是否在jobPartition列表
        if (StringUtils.isNotEmpty(job.getJobPartitions())) {
            if (!job.getJobPartitions().contains(IPUtil.getLocalIP())) {
                deleteJob(job.getJobName(), job.getJobGroup());
                return;
            }
        }

        Scheduler scheduler = schedulerFactoryBean.getScheduler();

        JobKey jobKey = JobKey.jobKey(job.getJobName(), job.getJobGroup());

        TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());

        CronTrigger newTrigger = TriggerBuilder.newTrigger()
                .withIdentity(job.getJobName(), job.getJobGroup())
                .withSchedule(cronSchedule(job.getJobCron())).build();

        if (scheduler.checkExists(jobKey)) {
            //已存在. 更新cron表达式.
            CronTrigger oldTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (oldTrigger == null || !StringUtils.equals(oldTrigger.getCronExpression(), job.getJobCron())) {
                LOGGER.info("JOB任务[{}]更新Cron表达式，查询刷新JOB任务", job.getJobCode());
                scheduler.rescheduleJob(triggerKey, newTrigger);
            }

            JobDetail oldJobDetail = scheduler.getJobDetail(jobKey);
            if (oldJobDetail != null) {
                Object obj = oldJobDetail.getJobDataMap().get(ScheduleConstant.SCHEDULE_JOB);
                if (!job.equals(obj)) {
                    LOGGER.info("JOB任务[{}]数据更新，重建JOB上下文数据", job.getJobCode());
                    // issue 更新到scheduler中后,JobExecutionContext中拿到的依然是旧数据
                    // oldJobDetail.getJobDataMap().put(ScheduleConstant.SCHEDULE_JOB, job);
                    deleteJob(job.getJobName(), job.getJobGroup());

                    createJob(job);
                }
            }
        } else {
            JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity(jobKey).build();
            jobDetail.getJobDataMap().put(ScheduleConstant.SCHEDULE_JOB, job);

            scheduler.scheduleJob(jobDetail, newTrigger);
            LOGGER.info("创建JOB[code={},desc={}]成功", job.getJobCode(), job.getJobDesc());
        }
    }

}
